Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

您所在的位置:网站首页 stm32f4 pwm输出 Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

2023-06-13 18:47| 来源: 网络整理| 查看: 265

Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发 Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发 Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发 Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构 Keil MDK STM32系列(六) 基于HAL的ADC模数转换 Keil MDK STM32系列(七) 基于HAL的PWM和定时器 Keil MDK STM32系列(八) 基于HAL的PWM和定时器输出音频 Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写 Keil MDK STM32系列(十) Ubuntu下的PlatformIO开发环境 方式1: 通过PWM和TIM输出音频

机制

音频使用一个预生成的的8bit无符号数组, 采样率为8KHz 输出包含两部分, 一部分是TIM2产生连续的PWM, PWM分辨率设置为256, 正好对应8bit PCM采样 输出的第二部分是TIM3产生的定时中断, 中断的频率正好是8KHz, 每次中断都修改一次PWM的占空比 通过调节PWM频率可以调节输出音质, PWM频率越高音质越好(谐振频率越远离音频) 通过调节PWM分辨率可以调节音量, PWM分辨率越高, 音量越低 配置STM32CubeMX

选择芯片STM32F401CCU6, 创建新项目

系统时钟 System Core -> SYS-> Debug: Serial Wire System Core -> RCC-> High Speed Clock (HSE): Crystal/Ceramic Resonator 启用外接高速晶振 Clock Configuration: (配置为最高84MHz)选择外部晶振, 连接HSE和PLLCLK, 在HCLK上输入84回车, 软件会自动调节各节点倍数 PWM(使用TIM2) Timers -> TIM2 Clock Source: Internel Clock, 使用系统的时钟源 Channel1: PWM Generation CH1 Counter Settings PWM频率 = 84MHz / (Perscaler + 1) / (Counter Period + 1) Perscaler: 0 Counter Mode: Up Counter Period: 255 Internal Clock Division(CKD): No Division auto-reload preload: Enable Trigger Output Master/Slave Mode (MSM bit): Disable Trigger Event Selection: Reset (UG bit from TIMx_EGR) PWM Generation Channel 1 Mode: PWM mode 1 Pulse: 0 Output compare perload: Enable Fast Mode: Disable CH Polarity: High 8KHz定时中断(使用TM3) Timers -> TIM3 勾选 Internal Clock Counter Settings Prescaler: 0 Counter Mode: Up Counter Period: 10499 # 10500 = 84MHz / 8KHz Internal Clock Division (CKD): No division auto-reload preload: Disable Trigger Output (TRGO) Parameters Master/Slave Mode (MSM bit): Disable Trigger Envent Selection: Reset NVIC Settings TIM3 global interrupt: Enable 代码修改

通过STM32CubeMX生成代码后, 需要对main.c添加代码

/* USER CODE BEGIN PV */ uint8_t pwm_buf[] = {125, 125, ..., 126, 125}; // 这里是一个长数组, 可以自己通过工具生成 uint8_t *start = pwm_buf, *end = pwm_buf, *lb = pwm_buf, *rb = (pwm_buf + 27451); // 27451是数组长度 /* USER CODE END PV */

main函数

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); MX_TIM3_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); HAL_TIM_Base_Start_IT(&htim3); /* USER CODE END 2 */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } }

添加定时器中断处理函数

/* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance==TIM3) { __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, *start++); if (start == rb) { start = lb; } } } /* USER CODE END 4 */ 输出效果演示

https://www.bilibili.com/video/BV1pb4y1177L

方式2: 通过PWM+DMA

通过配置成DMA的方式, 可以省掉一个定时器, 并且不需要主进程介入而直接将数组赋值给PWM.

这里有个需要注意的地方, STM32F401的各个TIMx计数器位宽不同, TIM2,TIM5是32bit, 其它的都是16bit, 而STM32F103的TIMx全是16bit位宽的. 之前在这个问题上困惑了很长时间, 后来费了不少工夫测试, 加上对比其它项目代码的配置才找到原因.

在设置DMA时, DMA_HandleTypeDef.Init.PeriphDataAlignment要与TIMx的计数器位宽一致, 如果没设置成一致会导致PWM输出错误. 而MemDataAlignment要与数组的数据类型一致, 实际上也要设置成对应的位宽.

根据ST的手册如果勾选了FIFO, 可以设置为其它位宽, 系统会自动补位, 但是实际测试并不能, 无论如何调整FIFOThreshold, MemBurst, 音频的前半部分都是错误的, 只能播放后半部分. 原因待查.

配置STM32CubeMX

选择芯片STM32F401CCU6, 创建新项目

系统时钟 System Core -> SYS-> Debug: Serial Wire System Core -> RCC-> High Speed Clock (HSE): Crystal/Ceramic Resonator 启用外接高速晶振 Clock Configuration: (配置为最高84MHz)选择外部晶振, 连接HSE和PLLCLK, 在HCLK上输入84回车, 软件会自动调节各节点倍数 PWM(使用TIM3) Timers -> TIM3 Internel Clock: 勾选, 使用系统的时钟源 Channel1: PWM Generation CH1 Counter Settings PWM频率 = 84MHz / (Perscaler + 1) / (Counter Period + 1) Perscaler: 40 Counter Mode: Up Counter Period: 255 Internal Clock Division(CKD): No Division auto-reload preload: Enable Trigger Output Master/Slave Mode (MSM bit): Disable Trigger Event Selection: Reset (UG bit from TIMx_EGR) PWM Generation Channel 1 Mode: PWM mode 1 Pulse: 0 Output compare perload: Enable Fast Mode: Disable CH Polarity: High

DMA Settings: Add

DMA Request: TIM3_CH1/Trig Stream: DMA1 Stream4 Direction: Memory To Peripheral Priority: High Mode: Circular Increment Address: Peripheral[不勾选], Memory[勾选] Use Fifo: 不勾选 Data Width: Peripheral[Half Word], Memory[Half Word] 代码修改

只需要在main.c中添加变量和启动方法

/* USER CODE BEGIN PV */ uint16_t pwm_buffer[] = {125, 125, 128, ...}; /* USER CODE END PV */ //... MX_TIM3_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t *)pwm_buffer, 27452); /* USER CODE END 2 */

在PA6上就能观察到PWM, 接上喇叭能听到输出. 这种方式因为基频8KHz就在人耳的听觉范围内, 会有持续的明显的高频声, 通过增加RC低通滤波能改善但是无法消除, 最好的方式还是将基频提升到20KHz以上, 这样基本上就不会被人耳感知了.

参考 详细说明了STM32的DMA工作方式 https://vivonomicon.com/2019/07/05/bare-metal-stm32-programming-part-9-dma-megamix/ DMA+PWM的位宽讨论 https://community.st.com/s/question/0D50X0000C6bAMdSQM/hal-timers-dma-method-enforces-4bytes-alignment-why- 另一个位宽相关的讨论 https://community.st.com/s/question/0D50X0000B45uUx/generation-of-pwm-wave-with-dma


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3